import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class UdpToTcpBroadcaster {

    private final ServerConfiguration serverConfiguration;
    private final Logger logger;
    private final List<InetSocketAddress> tcpTargets = new ArrayList<>();

    private final EventLoopGroup udpGroup = new NioEventLoopGroup();
    private final EventLoopGroup tcpGroup = new NioEventLoopGroup();

    // 保存所有 TCP 连接的 channel
    private final ConcurrentHashMap<InetSocketAddress, Channel> tcpChannels
            = new ConcurrentHashMap<>();
    // 保存所有转发链路的最后失败时间，一段时间内不要进行重连测试
    private final int minimumReconnectTimeInterval = 10*1000;
    private final ConcurrentHashMap<InetSocketAddress, Long> tcpChannelsLostTime
            = new ConcurrentHashMap<>();

    public UdpToTcpBroadcaster(
            ServerConfiguration serverConfiguration,
            Logger logger
    ) {
        this.serverConfiguration = serverConfiguration;
        this.logger = logger;

        for (String key : serverConfiguration.tcpTargetList.keySet())
        {
            TcpTarget tcpTarget = serverConfiguration.tcpTargetList.get(key);
            InetSocketAddress inetSocketAddress = new InetSocketAddress(
                    tcpTarget.tcp_host,
                    tcpTarget.tcp_port
            );
            tcpTargets.add(inetSocketAddress);
        }
    }

    public void start() throws InterruptedException {
        Bootstrap tcpBootstrap = new Bootstrap();
        tcpBootstrap.group(tcpGroup)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {}
                });

        // 2. 启动 UDP 服务
        Bootstrap udpBootstrap = new Bootstrap();
        udpBootstrap.group(udpGroup)
                .channel(NioDatagramChannel.class)
                .handler(new SimpleChannelInboundHandler<DatagramPacket>() {
                    @Override
                    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {
                        // 获取发送方的 IP 和端口
                        InetSocketAddress sender = packet.sender();
                        // String senderIp = sender.getAddress().getHostAddress();
                        int senderPort = sender.getPort();
                        if (senderPort == serverConfiguration.filter_udp_port)
                        {
                            // 本地回环流量，不会进行任何处理
                            return;
                        }

                        // 复制收到的消息内容到数组
                        ByteBuf buf = packet.content();
                        byte[] data = new byte[buf.readableBytes()];
                        buf.getBytes(buf.readerIndex(), data);

                        // 如果调试状态下打印收到的内容
                        if (logger.isTraceEnabled()) {
                            for (String s : Utils.bytesToHexList(data)) {
                                logger.trace(s);
                            }
                        }

                        //  第一次转发，要建立一个长连接
                        if (tcpChannels.isEmpty())
                        {
                            for (InetSocketAddress target : tcpTargets) {
                                try {
                                    logger.info("[SERVER] 正在尝试首次建立到目标[{}:{}]的转发链路...", target.getAddress(), target.getPort());
                                    ChannelFuture f = tcpBootstrap.connect(target).sync();

                                    if (f.isSuccess()) {
                                        logger.info("[SERVER] 成功建立到目标[{}:{}]的转发链路",
                                                target.getAddress(), target.getPort());
                                        // 成功处理逻辑
                                    } else {
                                        logger.error("[SERVER] 连接失败: {}:{}",
                                                target.getAddress(), target.getPort());
                                    }
                                    tcpChannels.put(target, f.channel());
                                }
                                catch (InterruptedException interruptedException)
                                {
                                    logger.error("[SERVER] 尝试首次建立到目标[{}:{}]的转发链路被中断.",
                                            target.getAddress(), target.getPort(), interruptedException);
                                } catch (Exception e) {
                                    logger.error("[SERVER] 尝试首次建立到目标[{}:{}]的转发链路失败:",
                                            target.getAddress(), target.getPort(), e);
                                }
                            }
                        }

                        // 广播给所有 TCP 长连接
                        for (Map.Entry<InetSocketAddress, Channel> targetItem : tcpChannels.entrySet()) {
                            Channel tcpCh = targetItem.getValue();
                            InetSocketAddress targetAddress = targetItem.getKey();
                            if (!tcpCh.isActive())
                            {
                                if (tcpChannelsLostTime.containsKey(targetAddress))
                                {
                                    Long lastReconnectTime = tcpChannelsLostTime.get(targetAddress);
                                    if (System.currentTimeMillis() - lastReconnectTime < minimumReconnectTimeInterval)
                                    {
                                        // 在指定的事件内不会反复去没有必要的尝试重新连接
                                        continue;
                                    }
                                }
                                // 远程的TCP端口已经不再活动，将尝试重新连接
                                try {
                                    logger.info("[SERVER] 正在尝试重新建立到目标[{}:{}]的转发链路...", targetAddress.getAddress(), targetAddress.getPort());
                                    ChannelFuture f = tcpBootstrap.connect(targetAddress).sync();

                                    if (f.isSuccess()) {
                                        logger.info("[SERVER] 成功建立到目标[{}:{}]的转发链路",
                                                targetAddress.getAddress(), targetAddress.getPort());
                                        tcpChannels.put(targetAddress, f.channel());
                                    } else {
                                        logger.error("[SERVER] 连接失败: {}:{}",
                                                targetAddress.getAddress(), targetAddress.getPort());
                                        // 保存当前失败时间，避免反复重试连接
                                        tcpChannelsLostTime.put(targetAddress, System.currentTimeMillis());
                                    }
                                }
                                catch (InterruptedException interruptedException)
                                {
                                    logger.error("[SERVER] 尝试重新建立到目标[{}:{}]的转发链路被中断.",
                                            targetAddress.getAddress(), targetAddress.getPort(), interruptedException);
                                    // 保存当前失败时间，避免反复重试连接
                                    tcpChannelsLostTime.put(targetAddress, System.currentTimeMillis());
                                } catch (Exception e) {
                                    logger.error("[SERVER] 尝试重新建立到目标[{}:{}]的转发链路失败:",
                                            targetAddress.getAddress(), targetAddress.getPort(), e);
                                    // 保存当前失败时间，避免反复重试连接
                                    tcpChannelsLostTime.put(targetAddress, System.currentTimeMillis());
                                }
                            }

                            // 重新获取Channel，有可能被重新连接过
                            tcpCh = tcpChannels.get(targetAddress);
                            if (tcpCh.isActive()) {
                                try {
                                    tcpCh.writeAndFlush(Unpooled.wrappedBuffer(data)).sync();
                                }
                                catch (InterruptedException interruptedException)
                                {
                                    logger.error("[SERVER] 转发数据到目标[{}:{}]的转发链路被中断.",
                                            targetAddress.getAddress(), targetAddress.getPort(), interruptedException);
                                    tcpChannelsLostTime.put(targetAddress, System.currentTimeMillis());
                                } catch (Exception e) {
                                    logger.error("[SERVER] 转发数据到目标到目标[{}:{}]的转发链路失败:",
                                            targetAddress.getAddress(), targetAddress.getPort(), e);
                                    tcpChannelsLostTime.put(targetAddress, System.currentTimeMillis());
                                }
                            }
                        }
                    }
                });
        ChannelFuture future = udpBootstrap.bind(serverConfiguration.udp_port).sync();
        Channel ch = future.channel();
        try {
            // 如果配置了multicast_if，则使用指定网卡, 否则使用本地信息
            NetworkInterface ni = Utils.getInterfaceByCidr(serverConfiguration.getUdp_multicast_if());
            if (ni == null)
            {
                ni = NetworkInterface.getByInetAddress(InetAddress.getLocalHost());
            }
            ((NioDatagramChannel) ch).joinGroup(
                    InetAddress.getByName(serverConfiguration.udp_host),
                    ni,
                    null).sync();
            logger.info("[SERVER] Forwarder has opened at [{}:{}].",
                    serverConfiguration.udp_host,
                    serverConfiguration.udp_port);
        }
        catch (Exception socketException)
        {
            throw new ServerException("[SERVER] Init local udp channel failed. ", socketException);
        }
        future.channel().closeFuture().sync();
    }

    public void stop() {
        tcpGroup.shutdownGracefully();
        udpGroup.shutdownGracefully();
    }
}
